use super::{
    err, err_value, json, ok, throw_err, Base, ECode, MapSv, Resp, SvCache, VBase, VMap, Value,
};
use std::time::Instant;
use tracing::{info, instrument};

pub struct Plc {
    cache: SvCache,
}

impl Base for Plc {}

impl Plc {
    pub async fn do_cmd(&self, para: &Value, cmd: &str) -> Resp {
        let data = para.ckobj()?;

        match cmd {
            "clear" => self.do_clear(data).await,
            "put" => self.do_put(data).await,
            "get" => self.do_get(data).await,
            _ => throw_err!(ECode::NoServe, "no cmd"),
        }
    }

    #[instrument(skip_all, name = "clear")]
    async fn do_clear(&self, data: &MapSv) -> Resp {
        let line = data.ck_str("line")?;
        let sn = data.ck_str("sn")?;
        let val = data.ck_array("v")?;

        let key = format!("{line}{sn}");

        if let Some(mut d) = self.cache.get(&key) {
            for k in val.iter() {
                let k = k.ckstr()?;
                d.remove(k);
            }

            self.cache.insert(key, d).await;
        }

        ok!()
    }

    #[instrument(skip_all, name = "put")]
    async fn do_put(&self, data: &MapSv) -> Resp {
        let start = Instant::now();

        let line = data.ck_str("line")?;
        let sn = data.ck_str("sn")?;
        let val = data.ck_obj("v")?;

        let key = format!("{line}{sn}");

        match self.cache.get(&key) {
            Some(mut d) => {
                for (k, v) in val.iter() {
                    d.insert(k.to_string(), v.clone());
                }

                self.cache.insert(key, d).await;
            }
            None => {
                self.cache.insert(key, val.clone()).await;
            }
        }

        let duration = start.elapsed();
        info!("put time: {:?}", duration);

        ok!()
    }

    #[instrument(skip_all, name = "get")]
    async fn do_get(&self, data: &MapSv) -> Resp {
        let start = Instant::now();

        let line = data.ck_str("line")?;
        let sn = data.ck_str("sn")?;

        let key = format!("{line}{sn}");

        let body = self
            .cache
            .get(&key)
            .ok_or(err_value(ECode::EmptyData, "no data"))?;

        let duration = start.elapsed();
        info!("put time: {:?}", duration);

        ok!(body)
    }

    pub fn new(cache: SvCache) -> Self {
        Plc { cache }
    }
}
